package com.gitee.l0km.casban;

import com.gitee.l0km.aocache.annotations.AoWeakCacheable;
import com.gitee.l0km.casban.annotations.AnnotationFieldNames;
import com.gitee.l0km.casban.annotations.CasbanBasepackage;
import com.gitee.l0km.com4j.basex.bean.BeanPropertySupport;
import com.gitee.l0km.com4j.basex.reflection.MethodUtils;
import com.gitee.l0km.common.spring.core.annotation.AnnotationUtils;
import com.gitee.l0km.common.spring.core.annotation.MergedAnnotation;
import com.gitee.l0km.common.spring.core.annotation.RepeatableContainers;
import com.gitee.l0km.common.spring.core.annotation.TypeMappedAnnotations;
import com.gitee.l0km.common.spring.core.annotation.MergedAnnotations.SearchStrategy;
import com.google.common.base.Function;
import com.google.common.base.Functions;
import com.google.common.base.Joiner;
import com.google.common.base.Optional;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Multimap;
import com.google.common.primitives.Booleans;
import com.google.common.primitives.Chars;
import com.google.common.primitives.Doubles;
import com.google.common.primitives.Floats;
import com.google.common.primitives.Ints;
import com.google.common.primitives.Longs;
import com.google.common.primitives.Shorts;

import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Strings.isNullOrEmpty;


public class CommonFunctions {
	@SuppressWarnings("unchecked")
	public static <T> Function<T, String> removePrefixFun(String prefix){
		return (Function<T, String>) new RemovePrefix(prefix);
	}
	
	private static class RemovePrefix implements Function<Object, String>{
		private final String prefix;
		private RemovePrefix(String prefix) {
			super();
			checkArgument(!isNullOrEmpty(prefix),"prefix is null or empty");
			this.prefix = prefix;
		}
		@Override
		public String apply(Object input) {
			String s = String.valueOf(input);
			if(CommonPredicates.startsWithFilter(prefix).apply(s)) {
				return s.substring(prefix.length());
			}
			return s;
		}
	}
	@SuppressWarnings("unchecked")
	@AoWeakCacheable(maximumSize = 200, expireAfterAccess = 20, expireAfterAccessTimeUnit = TimeUnit.SECONDS)
	public static <T> Function<T, String> removeSuffixFun(String suffix){
		return (Function<T, String>) new RemoveSuffix(suffix);
	}
	
	private static class RemoveSuffix implements Function<Object, String>{
		private final String suffix;
		private RemoveSuffix(String suffix) {
			super();
			this.suffix = checkNotNull(suffix,"suffix is null") ;
		}
		@Override
		public String apply(Object input) {
			String s = String.valueOf(input);
			if(!suffix.isEmpty()&&CommonPredicates.endsWithFilter(suffix).apply(s)) {
				return s.substring(0,s.length() - suffix.length());
			}
			return s;
		}
	}
	@SuppressWarnings("unchecked")
	@AoWeakCacheable(expireAfterAccess = 20, expireAfterAccessTimeUnit = TimeUnit.SECONDS)
	public static <T> Function<T, String> toStringFun(){
		return (Function<T, String>) new Function<Object, String>() {
			@Override
			public String apply(Object input) {
				return String.valueOf(input);
			}
		};
	}
	@AoWeakCacheable(expireAfterAccess = 20, expireAfterAccessTimeUnit = TimeUnit.SECONDS)
	public static Function<Class<?>, String> classNameFun(){
		return new Function<Class<?>, String>() {
			@Override
			public String apply(Class<?> input) {
				return input.getName();
			}
		};
	}
	@SuppressWarnings({ "unchecked", "rawtypes" })
	@AoWeakCacheable(expireAfterAccess = 20, expireAfterAccessTimeUnit = TimeUnit.SECONDS)
	public static <E extends Enum>  Function<E, String> enumNameFun(){
		return (Function<E, String>) new Function<Enum, String>() {
			@Override
			public String apply(Enum input) {
				return input.name();
			}
		};
	}
	
	@AoWeakCacheable(expireAfterAccess = 20, expireAfterAccessTimeUnit = TimeUnit.SECONDS)
	public static Function<Class<?>, Package> packageOfClassFun(){
		return new Function<Class<?>, Package>() {
			@Override
			public Package apply(Class<?> input) {
				return input.getPackage();
			}
		};
	}
	@AoWeakCacheable(expireAfterAccess = 20, expireAfterAccessTimeUnit = TimeUnit.SECONDS)
	public static Function<Class<?>, String> basePakageOfClassFun() {
		return new Function<Class<?>, String>() {
			@Override
			public String apply(Class<?> input) {
				String pkg = input.getPackage().getName();
				MergedAnnotation<CasbanBasepackage> man = TypeMappedAnnotations.from(input).get(CasbanBasepackage.class);
				if(man.isDirectlyPresent()) {
					int level = man.getInt(AnnotationFieldNames.LEVEL);
					int lastDot;
					while(level>0) {
						lastDot = pkg.lastIndexOf('.');
						if(lastDot > 0) {
							pkg = pkg.substring(0,lastDot);
							--level;
						}else {
							/** level太大忽略,直接返回类的包名 */
							return input.getPackage().getName();
						}
					}
				}
				return pkg;
			}
		};
	}
	@SuppressWarnings("unchecked")
	@AoWeakCacheable(expireAfterAccess = 20, expireAfterAccessTimeUnit = TimeUnit.SECONDS)
	public static <T> Function<T, String> getNameFun(){
		return (Function<T, String>) new Function<Object, String>(){
			@Override
			public String apply(Object input) {
				try {
					if(null!=input) {
						try {
							Method method = MethodUtils.getAccessibleMethod(input.getClass(), "getName",new Class<?>[]{});
							return (String)method.invoke(input);
						} catch (Exception e) {
							Throwables.throwIfUnchecked(e);
							throw new RuntimeException(e);
						}
					}
					return null;
				} catch (Exception e) {
					Throwables.throwIfUnchecked(e);
					throw new RuntimeException(e);
				} 
			}
		};
	}
	@SuppressWarnings("unchecked")
	@AoWeakCacheable(expireAfterAccess = 20, expireAfterAccessTimeUnit = TimeUnit.SECONDS)
	public static <T> Function<T, Integer> sizeOfFun(){
		return (Function<T, Integer>) new Function<Object, Integer>(){

			@Override
			public Integer apply(Object object) {
				return sizeOf(object);
			}};
	}
	@SuppressWarnings("rawtypes")
	static Integer sizeOf(Object object) {
		if(null == object) {
			return null;
		}
		if(object instanceof CharSequence) {
			return ((CharSequence)object).length();
		}
		if(object instanceof Collection) {
			return ((Collection)object).size();
		}
		if(object instanceof Iterable) {
			return Iterables.size((Iterable)object);
		}
		if(object.getClass().isArray()) {
			return Array.getLength(object);
		}
		if(object instanceof Map) {
			return ((Map)object).size();
		}
		if(object instanceof Optional) {
			return sizeOf(((Optional)object).orNull());
		}
		if(object instanceof Multimap) {
			return ((Multimap)object).size();
		}
		return 1;
	}
	
	@SuppressWarnings("unchecked")
	@AoWeakCacheable(expireAfterAccess = 20, expireAfterAccessTimeUnit = TimeUnit.SECONDS)
	public static <T> Function<T, T> nullIfEmptyFun(){
		return (Function<T, T>) new Function<Object, Object>() {

			@Override
			public Object apply(Object input) {
				return BeanPropertySupport.isEmpty(input) ? null : input;
			}
		};
	}
	
	@AoWeakCacheable(maximumSize = 200, expireAfterAccess = 20, expireAfterAccessTimeUnit = TimeUnit.SECONDS)
	public static <T> Function<T, T> nullToDefaultFun(T defaultValue){
		return new NullToDefault<T>(defaultValue);
	}
	private static class NullToDefault<T> implements Function<T, T>{
		private T defaultValue;
		public NullToDefault(T defaultValue) {
			this.defaultValue = checkNotNull(defaultValue,"defaultValue is null");
		}
		@Override
		public T apply(T input) {
			return null == input ? defaultValue : input;
		}
		
	}
	@AoWeakCacheable(expireAfterAccess = 20, expireAfterAccessTimeUnit = TimeUnit.SECONDS)
	public static Function<String, String> normalizePackageNameFun(){
		return new Function<String, String>() {
			
			@Override
			public String apply(String packagename) {
				if(!isNullOrEmpty(packagename) && packagename.charAt(packagename.length()-1) != '.') {
					packagename = packagename.concat(".");
				}
				return packagename;
			}
		};
	}
	@AoWeakCacheable(expireAfterAccess = 20, expireAfterAccessTimeUnit = TimeUnit.SECONDS)
	public static Function<String, String> packageNameToPathFun(){
		return new Function<String, String>() {
			
			@Override
			public String apply(String packagename) {
				return normalizePackageNameFun().apply(packagename).replace('.', '/');
			}
		};
	}
	public static final Function<Object, String> joinFun(String separator){
		return new JoinString(checkNotNull(separator,"separator is null"));
	}
	@AoWeakCacheable(expireAfterAccess = 20, expireAfterAccessTimeUnit = TimeUnit.SECONDS)
	public static final Function<Object, String> joinFun(){
		return new JoinString(",");
	}
	private static class JoinString implements Function<Object,String>{
		private final String separator;
		private JoinString(String separator) {
			this.separator = separator ;
		}
		@Override
		public String apply(Object input) {
			return join(separator, input);
		}
		
	}
	
	static String join(String separator,Object object) {
		if(null == object) {
			return "";
		}
		if(object instanceof CharSequence) {
			return ((CharSequence)object).toString();
		}
		if(object instanceof Iterable) {
			return Joiner.on(separator).join((Iterable<?>)object);
		}
		if(object.getClass().isArray()) {
			Class<?> componentType = object.getClass().getComponentType();
//			if(componentType.equals(Class.class)) {
//				return join(separator,FluentIterable.from((Class<?>[])object).transform(classNameFun()));
//			}
//			if(Enum.class.isAssignableFrom(componentType)) {
//				return join(separator,FluentIterable.from((Enum[])object).transform(CommonFunctions.<Enum>enumNameFun()));
//			}
			if(!componentType.isPrimitive()) {
				return Joiner.on(separator).join((Object[])object);
			}else  if(boolean.class.equals(componentType)){
				return Booleans.join(separator, (boolean[])object);
			}else  if(char.class.equals(componentType)){
				return Chars.join(separator, (char[])object);
			}else  if(double.class.equals(componentType)){
				return Doubles.join(separator, (double[])object);
			}else  if(float.class.equals(componentType)){
				return Floats.join(separator, (float[])object);
			}else if(int.class.equals(componentType)){
				return Ints.join(separator, (int[])object);
			}else  if(long.class.equals(componentType)){
				return Longs.join(separator, (long[])object);
			}else  if(short.class.equals(componentType)){
				return Shorts.join(separator, (short[])object);
			}
		}
		throw new UnsupportedOperationException("UNSUPPORTED TYPE "+ object.getClass().getName());
	}

	public static <A extends Annotation> Function<MergedAnnotation<A>, A>synthesizeFun(){
		return new SynthesizeFun<>();
	}
	
	private static class SynthesizeFun<A extends Annotation>implements Function<MergedAnnotation<A>, A>{

		@Override
		public A apply(MergedAnnotation<A> input) {
			return null == input ? null : input.synthesize();
		}
	}
	@SuppressWarnings("rawtypes")
	public static Function<MergedAnnotation, MergedAnnotation> rootOfMergedAnnotationFun() {
		return new Function<MergedAnnotation, MergedAnnotation>(){
			@Override
			public MergedAnnotation apply(MergedAnnotation input) {
				return null == input ? null : input.getRoot();
			}};
	}
	
	@SuppressWarnings("unchecked")
	public static <A extends Annotation> Function<AnnotatedElement, A> findAnnotationFun(Class<A> annotationType){
		return (Function<AnnotatedElement, A>) new FindAnnotationFun(annotationType);
	}
	private static class FindAnnotationFun implements Function<AnnotatedElement, Annotation>{
		private final Class<? extends Annotation> annotationType;
		public FindAnnotationFun(Class<? extends Annotation> annotationType) {
			this.annotationType = checkNotNull(annotationType,"annotationType is null") ;
		}
		@Override
		public Annotation apply(AnnotatedElement input) {
			if(input instanceof Method) {
				return AnnotationUtils.findAnnotation((Method)input,annotationType);
			}else if(input instanceof Class) {
				
				return AnnotationUtils.findAnnotation((Class<?>)input,annotationType);
			}
			return null == input ? null : AnnotationUtils.findAnnotation(input,annotationType);
		}
	}
	public static final Function<Annotation, Map<String, Object>> getAnnotationAttributesFun(){
		return new Function<Annotation, Map<String, Object>>(){

			@Override
			public Map<String, Object> apply(Annotation input) {
				return null==input ? ImmutableMap.<String, Object>of() : AnnotationUtils.getAnnotationAttributes(input);
			}};
	}
	/**
	 * 从{@link AnnotatedElement}对象(Class,Method,Field)返回指定注解的所有字段值
	 * @param <E>
	 * @param annotationType
	 */
	@SuppressWarnings("unchecked")
	@AoWeakCacheable(maximumSize = 200, expireAfterAccess = 20, expireAfterAccessTimeUnit = TimeUnit.SECONDS)
	public static <E extends AnnotatedElement> Function<E, Map<String, Object>> getAnnotationAttributesOfAnnotatedElementFun(Class<? extends Annotation> annotationType){
		return (Function<E, Map<String, Object>>) Functions.compose(getAnnotationAttributesFun(), findAnnotationFun(annotationType));
	}
	
	private static final class GetMeragedAnnotationFun<A extends Annotation> implements	 Function<AnnotatedElement, MergedAnnotation<A>>{
		private Class<A>annotationType;
		public GetMeragedAnnotationFun(Class<A> annotationType) {
			this.annotationType = checkNotNull(annotationType,"annotationType is null");
		}
		@Override
		public MergedAnnotation<A> apply(AnnotatedElement input) {
			return TypeMappedAnnotations.from(input, SearchStrategy.INHERITED_ANNOTATIONS, RepeatableContainers.none())
					.get(annotationType).withNonMergedAttributes();
		}}; 
		
	/**
	 * @since 1.1.0
	 */
	@AoWeakCacheable(maximumSize = 200, expireAfterAccess = 20, expireAfterAccessTimeUnit = TimeUnit.SECONDS)
	public static <A extends Annotation> Function<AnnotatedElement, MergedAnnotation<A>> getMeragedAnnotationFun(Class<A> annotationType){
		return new GetMeragedAnnotationFun<A>(annotationType);
	}
	public static Function<? extends AnnotatedElement, Member> asMemberFun() {
		return new Function<AnnotatedElement, Member>(){

			@Override
			public Member apply(AnnotatedElement input) {
				return (Member)input;
			}};
	}
	public static Function<Member, ? extends AnnotatedElement> asAnnotatedElementFun() {
		return new Function<Member, AnnotatedElement>(){
			
			@Override
			public AnnotatedElement apply(Member input) {
				return (AnnotatedElement)input;
			}};
	}
	
	private static class TypeCastOrnullFun<F,A> implements Function<F, A>{
		private final Class<A> type;
		TypeCastOrnullFun(Class<A> type) {
			this.type = type;
		}
		@Override
		public A apply(F input) {
			return type.isInstance(input) ? type.cast(input):null;
		}
	}
	@AoWeakCacheable(expireAfterAccess = 20, expireAfterAccessTimeUnit = TimeUnit.SECONDS)
	public static Function<Member, Method> asMethodOrnull() {
		return new TypeCastOrnullFun<>(Method.class);
	}
	@AoWeakCacheable(expireAfterAccess = 20, expireAfterAccessTimeUnit = TimeUnit.SECONDS)	
	public static Function<Member, Field> asFieldOrnull() {
		return new TypeCastOrnullFun<>(Field.class);
	}
	
	@SuppressWarnings("rawtypes")
	@AoWeakCacheable(expireAfterAccess = 20, expireAfterAccessTimeUnit = TimeUnit.SECONDS)
	public static Function<Member, Constructor> asConstructorOrnull() {
		return new TypeCastOrnullFun<>(Constructor.class);
	}
	
}
